package home.project.misc;

import openfl.display.Graphics;
import openfl.geom.Point;
import openfl.geom.Rectangle;

/**
 * ...
 * @author an.vuongngocduy
 */
class Quadtree extends Entity {
	
	private static inline var __WEST:Int = -1;
	private static inline var __EAST:Int = -2;
	private static inline var __NORTH:Int = -3;
	private static inline var __SOUTH:Int = -4;
	private static inline var __NORTH_WEST:Int = 0;
	private static inline var __NORTH_EAST:Int = 1;
	private static inline var __SOUTH_EAST:Int = 2;
	private static inline var __SOUTH_WEST:Int = 3;
	
	private var __rect:Rect;
	private var __center:Point;
	private var __level:Int;
	private var __tolerance:Int;
	
	private var __root:Quadtree;
	private var __parentTree:Quadtree;
	private var __childTrees:Array<Quadtree>;
	
	private var __bounds:Array<Rect>;
	
	public function new(p_x:Float, p_y:Float, p_width:Float, p_height:Float, p_parentTree:Quadtree = null) {
		super();
		position = new Point(p_x, p_y);
		__tolerance = 10;
		if (p_parentTree == null) {
			__root = this;
		}
		else {
			__parentTree = p_parentTree;
			__root = p_parentTree.__root;
		}
		__bounds = new Array<Rect>();	
		__level = (__parentTree == null) ? 0 : __parentTree.__level + 1;
		__rect = new Rect(this, absoluteTransform.tx, absoluteTransform.ty, p_width, p_height);
		__center = new Point(__rect.x + __rect.width / 2.0, __rect.y + __rect.height / 2.0);
	}
	
	public function push(p_entity:Entity, p_ignoreChildren:Bool = false):Void {
		if(!p_ignoreChildren){
			for (e in p_entity.__children) {
				push(e, p_ignoreChildren);
			}
		}
		
		if(!p_entity.isCollidable) return;
		
		for(bb in p_entity.boundingBoxes){
			__add(bb);
		}
	}
		
	public function clear():Void {
		if (__childTrees != null) {
			for (q in __childTrees) {
				q.clear();
			}
			__childTrees.splice(0, 4);
			__childTrees = null;
		}
		if(__bounds != null) {
			__bounds.splice(0, __bounds.length);
		}
	}
	
	public function traverse():Void {
		if (__childTrees != null) {
			for (t in __childTrees) {
				t.traverse();
			}
		}
		//! THIS IS A MESS , NEED TO TAKE CARE OF IT
		for(bound in __bounds){
			var suspectBounds:Array<Rect> = __root.__retrieve(bound);
			suspectBounds.remove(bound);
			for (checkedBound in suspectBounds) {
				if (bound.intersects(checkedBound)) {
					//! collision happens
				}
			}
		}
	}
	
	public override function draw(p_g:Graphics) {
	#if debug
		p_g.lineStyle(1.0, 0xFFFFFF, 1, true);
		for (b in __bounds) {
			p_g.moveTo(b.target.absoluteTransform.tx + b.left, b.target.absoluteTransform.ty + b.top);
			p_g.lineTo(b.target.absoluteTransform.tx + b.right, b.target.absoluteTransform.ty + b.top);
		
			p_g.moveTo(b.target.absoluteTransform.tx + b.right, b.target.absoluteTransform.ty + b.top);
			p_g.lineTo(b.target.absoluteTransform.tx + b.right, b.target.absoluteTransform.ty + b.bottom);
		
			p_g.moveTo(b.target.absoluteTransform.tx + b.right, b.target.absoluteTransform.ty + b.bottom);
			p_g.lineTo(b.target.absoluteTransform.tx + b.left, b.target.absoluteTransform.ty + b.bottom);
		
			p_g.moveTo(b.target.absoluteTransform.tx + b.left, b.target.absoluteTransform.ty + b.bottom);
			p_g.lineTo(b.target.absoluteTransform.tx + b.left, b.target.absoluteTransform.ty + b.top);
		}
		
		p_g.lineStyle(3.0, 0x00FFFF, 1, true);
		p_g.moveTo(__rect.left, __rect.top);
		p_g.lineTo(__rect.right, __rect.top);
		
		p_g.moveTo(__rect.right, __rect.top);
		p_g.lineTo(__rect.right, __rect.bottom);
		
		p_g.moveTo(__rect.right, __rect.bottom);
		p_g.lineTo(__rect.left, __rect.bottom);
		
		p_g.moveTo(__rect.left, __rect.bottom);
		p_g.lineTo(__rect.left, __rect.top);
		
		if (__childTrees == null) return;
		
		for (q in __childTrees) {
			q.draw(p_g);
		}
	#end
	}
	
	private function __add(p_rect:Rect):Void {
		if (__childTrees != null) {
			var idx:Int = __findindex(p_rect);
			if (idx >= 0) {
				__childTrees[idx].__add(p_rect);
				return;
			}
		}	
		
		__bounds.push(p_rect);
				
		if (__bounds.length > __tolerance) {		
			__subdivide();
			var i:Int = 0;
			while (i < __bounds.length) {
				var idx:Int = __findindex(__bounds[i]);
				if (idx < 0) {
					i++;
					continue;
				}
				__childTrees[idx].__add(__bounds[i]);
				__bounds.splice(i, 1);
			}
		}
	}
	
	private function __retrieve(p_rect:Rect):Array<Rect> {
		var res:Array<Rect> = new Array<Rect>();		
		var idx:Int = __findindex(p_rect);			
		if (idx >= 0) {
			if (__childTrees != null) {
				res = res.concat(__childTrees[idx].__retrieve(p_rect));
			}
		}
		res = res.concat(__bounds);
		return res;
	}
	
	private function __subdivide():Void {
		if (__childTrees != null) return;
		
		__childTrees = new Array<Quadtree>();
		
		__childTrees[__NORTH_WEST] = new Quadtree(__rect.x, __rect.y, __rect.width / 2.0, __rect.height / 2.0, this);
		__childTrees[__NORTH_EAST] = new Quadtree(__center.x, __rect.y , __rect.width / 2.0, __rect.height / 2.0, this);
		__childTrees[__SOUTH_WEST] = new Quadtree(__rect.x, __center.y, __rect.width / 2.0, __rect.height / 2.0, this);
		__childTrees[__SOUTH_EAST] = new Quadtree(__center.x, __center.y, __rect.width / 2.0, __rect.height / 2.0, this);
	}
	
	private function __findindex(p_rect:Rect):Int {
		var in_west:Bool = ((p_rect.target.absoluteTransform.tx + p_rect.left) > __rect.left) 
						&& ((p_rect.target.absoluteTransform.tx + p_rect.right) < __center.x);

		var in_east:Bool = ((p_rect.target.absoluteTransform.tx + p_rect.left) > __center.x) 
						&& ((p_rect.target.absoluteTransform.tx + p_rect.right) < __rect.right);
						
		var in_north:Bool = ((p_rect.target.absoluteTransform.ty + p_rect.top) > __rect.top) 
						&& ((p_rect.target.absoluteTransform.ty + p_rect.bottom) < __center.y);
		
		var in_south:Bool = ((p_rect.target.absoluteTransform.ty + p_rect.top) > __center.y) 
						&& ((p_rect.target.absoluteTransform.ty + p_rect.bottom) < __rect.bottom);

		var index:Int = in_west ? __WEST : ( in_east ? __EAST : -5);		
		if (in_north) {
			index = in_west ? __NORTH_WEST : (in_east ? __NORTH_EAST : __NORTH);
		}		
		if (in_south) {
			index = in_west ? __SOUTH_WEST : (in_east ? __SOUTH_EAST : __SOUTH);
		}
		return index;
	}
}